﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ServiceModel.Description;
using Microsoft.Xrm.Sdk.Client;
using Microsoft.Xrm.Sdk.Discovery;
using NerdWare.Crm2011VsTools.Core.Crm;

namespace NerdWare.CrmPlugin.Crm5
{
    [Export(typeof(IDiscoverCrm))]
    [ExportMetadata("CrmVersion", CrmVersionEnum.Crm2011)]
    public class Discovery : DiscoverCrmBase
    {
        public override void Discover()
        {
            IServiceManagement<IDiscoveryService> serviceManagement =
                ServiceConfigurationFactory.CreateManagement<IDiscoveryService>(base.CrmApplicationDescriptor.GetDiscoveryUri());
            AuthenticationProviderType endpointType = serviceManagement.AuthenticationType;

            // Set the credentials.
            AuthenticationCredentials authCredentials = GetCredentials(endpointType);

            using (DiscoveryServiceProxy discoveryServiceProxy = GetProxy<IDiscoveryService, DiscoveryServiceProxy>(serviceManagement, authCredentials))
            {
                RetrieveOrganizationsRequest organizationsRequest = new RetrieveOrganizationsRequest();

                RetrieveOrganizationsResponse organizationsResponse = (RetrieveOrganizationsResponse)discoveryServiceProxy.Execute(organizationsRequest);

                createOrganizationDictionary(organizationsResponse);
            }
        }

        /// <summary>
        /// Obtain the AuthenticationCredentials based on AuthenticationProviderType.
        /// </summary>
        /// <param name="endpointType">An AuthenticationProviderType of the CRM environment.</param>
        /// <returns>Get filled credentials.</returns>
        private AuthenticationCredentials GetCredentials(AuthenticationProviderType endpointType)
        {

            AuthenticationCredentials authCredentials = new AuthenticationCredentials();
            switch (endpointType)
            {
                case AuthenticationProviderType.ActiveDirectory:
                    authCredentials.ClientCredentials = base.CrmApplicationDescriptor.GetClientCredentials();
                    break;
                case AuthenticationProviderType.LiveId:
                    authCredentials.ClientCredentials = base.CrmApplicationDescriptor.GetClientCredentials();
                    authCredentials.SupportingCredentials = new AuthenticationCredentials();
                    authCredentials.SupportingCredentials.ClientCredentials = DeviceIdManager.LoadOrRegisterDevice();
                    break;
                default: // For Federated and OnlineFederated environments.                    
                    throw new NotImplementedException("Only Active Directory and LiveID is supported so far.");
                    authCredentials.ClientCredentials = base.CrmApplicationDescriptor.GetClientCredentials();
                    // For OnlineFederated single-sign on, you could just use current UserPrincipalName instead of passing user name and password.
                    // authCredentials.UserPrincipalName = UserPrincipal.Current.UserPrincipalName;  //Windows/Kerberos
                    break;
            }

            return authCredentials;
        }

        /// <summary>
        /// Generic method to obtain discovery/organization service proxy instance.
        /// </summary>
        /// <typeparam name="TService">
        /// Set IDiscoveryService or IOrganizationService type to request respective service proxy instance.
        /// </typeparam>
        /// <typeparam name="TProxy">
        /// Set the return type to either DiscoveryServiceProxy or OrganizationServiceProxy type based on TService type.
        /// </typeparam>
        /// <param name="serviceManagement">An instance of IServiceManagement</param>
        /// <param name="authCredentials">The user's Microsoft Dynamics CRM logon credentials.</param>
        /// <returns></returns>
        private TProxy GetProxy<TService, TProxy>(
            IServiceManagement<TService> serviceManagement,
            AuthenticationCredentials authCredentials)
            where TService : class
            where TProxy : ServiceProxy<TService>
        {
            Type classType = typeof(TProxy);

            if (serviceManagement.AuthenticationType !=
                AuthenticationProviderType.ActiveDirectory)
            {
                AuthenticationCredentials tokenCredentials =
                    serviceManagement.Authenticate(authCredentials);
                // Obtain discovery/organization service proxy for Federated, LiveId and OnlineFederated environments. 
                // Instantiate a new class of type using the 2 parameter constructor of type IServiceManagement and SecurityTokenResponse.
                return (TProxy)classType
                                    .GetConstructor(new Type[] { typeof(IServiceManagement<TService>), typeof(SecurityTokenResponse) })
                                    .Invoke(new object[] { serviceManagement, tokenCredentials.SecurityTokenResponse });
            }
            // Obtain discovery/organization service proxy for ActiveDirectory environment.
            // Instantiate a new class of type using the 2 parameter constructor of type IServiceManagement and ClientCredentials.
            return (TProxy)classType
                .GetConstructor(new Type[] { typeof(IServiceManagement<TService>), typeof(ClientCredentials) })
                .Invoke(new object[] { serviceManagement, authCredentials.ClientCredentials });

        }

        private void createOrganizationDictionary(RetrieveOrganizationsResponse organizationResponse)
        {
            base.Organizations = new List<OrgDetail>();
            foreach (OrganizationDetail organizationDetail in organizationResponse.Details)
            {
                OrgDetail orgDetail = new OrgDetail
                {
                    OrganizationName = organizationDetail.UrlName,
                    OrganizationId = organizationDetail.OrganizationId,
                    OrganizationVersion = organizationDetail.OrganizationVersion,
                    OrganizationFriendlyName = organizationDetail.FriendlyName,
                    CrmVersion = CrmVersionEnum.Crm2011
                };

                foreach (var endpoint in organizationDetail.Endpoints)
                {
                    switch (endpoint.Key)
                    {
                        case EndpointType.WebApplication:
                            orgDetail.WebAppUrl = endpoint.Value;
                            break;
                        case EndpointType.OrganizationService:
                            orgDetail.OrganizationService = endpoint.Value;
                            break;
                        case EndpointType.OrganizationDataService:
                            orgDetail.ODataService = endpoint.Value;
                            break;
                    }
                }

                base.Organizations.Add(orgDetail);
            }
        }
    }
}
